// frontend.cpp
// MobileSync-Inspect
// 
// Copyright (c) 2008 Lukhnos D. Liu (lukhnos at gmail dot com)
// All rights reserved.
// 
// In short, this is the New (3-Clause) BSD License.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. Neither the name of the software nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#include <iostream>
#include "MDBackupData.h"

namespace MobileSyncInspect {
	using namespace std;
	
    class FrontEnd {
    public:
        FrontEnd()
        {
        	#ifndef WIN32
        	    string mobileSyncPath = OVDirectoryHelper::UserApplicationSupportDataDirectory("MobileSync");
            #else
                string mobileSyncPath = OVPathHelper::PathCat(OVDirectoryHelper::UserApplicationDataDirectory("Apple Computer"), "MobileSync");
            #endif
        
	        m_mobileSyncBackupPath = OVPathHelper::PathCat(mobileSyncPath, "Backup");
        }
        
        void usage()
        {
            cerr << "usage:" << endl;
            cerr << "    list [pattern] - list backup files matching the pattern (wildcard)" << endl;
            cerr << "    extract <file> <output> - extract the data in file to output" << endl;
            cerr << "    replace <file> <source> - replace the data in file with source" << endl;
            cerr << "    backup <pattern> <directory> - backup files to direcotry" << endl;
            cerr << endl;
            cerr << "examples:" << endl;
            cerr << "    list *Monkey*" << endl;
            cerr << "    extract 8f3c7469/12456c4e.mdbackup /tmp/foo" << endl;
            cerr << "    replace 8f3c7469/12456c4e.mdbackup /tmp/bar" << endl;
            cerr << "    backup *Ball* /tmp/BallBackup" << endl;
            cerr << endl;
            cerr << "Note: There's no need to use the full path when using extract/replace." << endl;
            cerr << "Please read USAGE.txt before using the program." << endl;
            cerr << endl;
        	cerr << "MobileSync backup directory at: " << m_mobileSyncBackupPath << endl;
            cerr << endl;
        }
        
        int list(const string& pattern)
        {
            if (!checkBackupPathWithErrorMessage()) {
                return 1;
            }

        	OVWildcard filter(OVPathHelper::Normalize(pattern));
        	vector<string> backupFiles = OVDirectoryHelper::Glob(m_mobileSyncBackupPath, "*.mdbackup", "", 2);

        	for (vector<string>::iterator iter = backupFiles.begin() ; iter != backupFiles.end() ; ++iter) {
        		try {
        			MDBackup backup(*iter);
        			if (filter.match(OVPathHelper::Normalize(backup.path()))) {			
        				FileTimestampReader timestampReader(OVPathHelper::TimestampForPath(*iter));

                        cout << timestampReader.dateTimeShorthand() << " - " << (*iter).substr(m_mobileSyncBackupPath.size() + 1) << " : ";
						cout << OVPathHelper::Normalize(backup.path()) << " (" << backup.dataLength() << ")" << endl;
        			}
        		}
        		catch(MDBackupException) {
        			cerr << "cannot open: " << *iter << endl;
        		}
        	}
            
            return 0;
        }
        
        
        int extract(const string& file, const string& output)
        {
            if (!checkBackupPathWithErrorMessage()) {
                return 1;
            }
			
			string fullPath = OVPathHelper::PathCat(m_mobileSyncBackupPath, file);
            
    		try {
    			MDBackup backup(fullPath);

                cout << "Extracting content: " << backup.path() << " (" << backup.dataLength() << " bytes)" << endl;
                ofstream ofs;
                ofs.open(output.c_str(), ios_base::out | ios_base::binary);
                ofs.write((const char*)backup.dataBytes(), (streamsize)backup.dataLength());
                ofs.close();                        
    			
            }
    		catch(MDBackupException) {
    			cerr << "cannot open: " << fullPath << endl;
    		}
            
            return 0;
        }
        
        int replace(const string& file, const string& dataFile)
        {
            if (!checkBackupPathWithErrorMessage()) {
                return 1;
            }
			
			string fullPath = OVPathHelper::PathCat(m_mobileSyncBackupPath, file);
            
    		try {
    			MDBackup backup(fullPath);
				
                cout << "Replacing content: " << backup.path();
				if (!backup.replaceDataContentAndSave(dataFile)) {
					cerr << "Cannot replace data content using source file: " << dataFile << endl;
				}
            }
    		catch(MDBackupException) {
    			cerr << "cannot open: " << fullPath << endl;
    		}
            			
            return 0;
        }
        
        int backup(const string& pattern, const string& path)
        {
            if (!checkBackupPathWithErrorMessage()) {
                return 1;
            }
            
            if (!OVPathHelper::PathExists(path)) {
                cerr << "Output path doesn't exist: " << path << endl;
                return 1;
            }
            
            if (!OVPathHelper::IsDirectory(path)) {
                cerr << "Output path is not a directory: " << path << endl;
                return 1;
            }

        	OVWildcard filter(OVPathHelper::Normalize(pattern));
        	vector<string> backupFiles = OVDirectoryHelper::Glob(m_mobileSyncBackupPath, "*.mdbackup", "", 2);

        	for (vector<string>::iterator iter = backupFiles.begin() ; iter != backupFiles.end() ; ++iter) {
        		try {
        			MDBackup backup(*iter);
        			if (filter.match(OVPathHelper::Normalize(backup.path()))) {	
                        string filePath = OVPathHelper::Normalize(backup.path());        			    
                        string outputPath = OVPathHelper::PathCat(path, filePath);
                        string outputDir = OVPathHelper::DirectoryFromPath(outputPath);
                        
                        if (!OVPathHelper::PathExists(outputDir)) {
                            OVDirectoryHelper::MakeDirectoryWithImmediates(outputDir);
                        }
                        
                        cout << "Writing: " << filePath << " (" << backup.dataLength() << " bytes)" << endl;
                        ofstream ofs;
                        ofs.open(outputPath.c_str(), ios_base::out | ios_base::binary);
                        ofs.write((const char*)backup.dataBytes(), (streamsize)backup.dataLength());
                        ofs.close();                        
        			}
        		}
        		catch(MDBackupException) {
        			cerr << "cannot open: " << *iter << endl;
        		}
        	}
            
            return 0;
        }
        
		const string mobileSyncBackupPath() const
		{
			return m_mobileSyncBackupPath;
		}
        
    protected:
        bool checkBackupPathWithErrorMessage()
        {
        	cerr << "MobileSync backup directory at: " << m_mobileSyncBackupPath << endl;
            
        	if (!OVPathHelper::PathExists(m_mobileSyncBackupPath)) {
        		cerr << "Path doesn't exist: " << m_mobileSyncBackupPath << endl;
        		return false;
        	}
            return true;
        }

        string m_mobileSyncBackupPath;
    };
};

int main(int argc, char *argv[])
{
    MobileSyncInspect::FrontEnd frontend;
        
    if (argc < 2) {
        frontend.usage();
        return 1;
    }

	std::string command(argv[1]);

    if (command == "list") {
        if (argc > 2) {
            return frontend.list(argv[2]);
        }
        else {
            return frontend.list("*");
        }
    }
    else if (command == "extract") {
        if (argc < 4) {
            frontend.usage();
        }
        else {
            return frontend.extract(argv[2], argv[3]);
        }
    }
    else if (command == "replace") {
        if (argc < 4) {
            frontend.usage();
        }
        else {
            return frontend.replace(argv[2], argv[3]);
        }
    }
    else if (command == "backup") {
        if (argc < 4) {
            frontend.usage();
        }
        else {
            return frontend.backup(argv[2], argv[3]);
        }
    }
    else {
		std::cerr << "Cannot recognize command: " << command << std::endl;
    }
    
    return 1;
}
